//===============================================================================
// Microsoft patterns & practices
// Web Client Software Factory
//-------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//-------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================
using System;
using System.Globalization;
using System.Reflection;
using WCSFContrib.PageFlow.Configuration;
using WCSFContrib.PageFlow.WorkflowFoundation.Properties;
using System.Web;

namespace WCSFContrib.PageFlow.WorkflowFoundation
{
	/// <summary>
    /// Implementation that uses Windows Workflow Foundation as the page flow engine.
	/// </summary>
    /// <remarks>
    /// <para>This class is provided as a singleton by the <see cref="PageFlowDirectory"/> class</para>
    /// </remarks>
    public class WorkflowFoundationPageFlowProvider : IPageFlowProvider, IDisposable
	{
		private IPageFlowFactory _pageFlowFactory;
		private IPageFlowInstanceStore _store;        

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkflowFoundationPageFlowProvider" /> class with a page flow factory and instance store.
        /// </summary>
        /// <param name="factory">The page flow factory to use.</param>
        /// <param name="store">The instance store to use.</param>
		public WorkflowFoundationPageFlowProvider(IPageFlowFactory factory, IPageFlowInstanceStore store)
		{
			_pageFlowFactory = factory;
			_store = store;
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkflowFoundationPageFlowProvider" /> class with an instance store configuration section and a correlation token provider configuration section.
        /// </summary>
        /// <param name="pageFlowInstanceStoreProviderSection">The configuration section corresponding to the instance store.</param>
        /// <param name="pageFlowCorrelationTokenProviderSection">The configuration section corresponding to the correlation token provider.</param>
        public WorkflowFoundationPageFlowProvider(PageFlowInstanceStoreProviderSection pageFlowInstanceStoreProviderSection, PageFlowInstanceCorrelationTokenProviderSection pageFlowCorrelationTokenProviderSection)
		{
			if (pageFlowInstanceStoreProviderSection == null)
				throw new ArgumentNullException("pageFlowInstanceStoreProviderSection");

            if (pageFlowCorrelationTokenProviderSection == null)
                throw new ArgumentNullException("pageFlowCorrelationTokenProviderSection");
            
            _pageFlowFactory = new WorkflowFoundationPageFlowFactory();

            Type tokenProviderType = Type.GetType(pageFlowCorrelationTokenProviderSection.ProviderType, true, true);
            IPageFlowCorrelationTokenProvider _tokenProvider =
                (IPageFlowCorrelationTokenProvider)Activator.CreateInstance(tokenProviderType);
        
			Type storeType = Type.GetType(pageFlowInstanceStoreProviderSection.ProviderType, true, true);
			_store =
				(IPageFlowInstanceStore) Activator.CreateInstance(storeType, BindingFlags.CreateInstance, null,
				                                                  new object[]
				                                                  	{
				                                                  		pageFlowInstanceStoreProviderSection.ConnectionStringName,
                                                                        _tokenProvider
				                                                  	},
				                                                  CultureInfo.CurrentCulture);

            

		}
		
        /// <summary>
        /// Returns a new or existing page flow given the page flow type.
        /// </summary>
        /// <example>
        /// <code>
        /// MyPageFlow myPageFlow = PageFlowDirectory.Provider.GetPageFlow(typeof(MyPageFlow));
        /// </code>
        /// </example>
        /// <param name="pageFlowType">The page flow type to get.</param>
        /// <returns>An instance of a page flow</returns>
        /// <remarks>
        /// <para>
        /// If there are page flow instances (running or not) in the <see cref="IPageFlowInstanceStore" /> with the <paramref name="pageFlowType"/> specified,
        /// then the provider will get the instance id asociated and retrieve the instance from Workflow Foundation persistence service using the <see cref="WorkflowFoundationPageFlowFactory" />.
        /// </para>
        /// <para>
        /// If there are NO page flow instances in the <see cref="IPageFlowInstanceStore" /> with the <paramref name="pageFlowType"/> specified,
        /// then the provider will create a new one using the <see cref="WorkflowFoundationPageFlowFactory" /> and will add it to the store as a non running page flow.
        /// </para>
        /// </remarks>
        public IPageFlow GetPageFlow(Type pageFlowType)
        {
            return GetPageFlow(pageFlowType, true);
        }

        private IPageFlow GetPageFlow(Type pageFlowType, bool createIfNotExists)
        {
            Guid instanceId = _store.GetByType(pageFlowType);
            if (instanceId != Guid.Empty)
            {
                return GetPageFlow(pageFlowType, instanceId);
            }
            else if (createIfNotExists)
            {
                return CreatePageFlow(pageFlowType);
            }
            return null;
        }  

        /// <summary>
        /// Returns an existing page flow given the instance id.
        /// </summary>
        /// <example>
        /// <code>
        /// Guid myPageFlowId = GetPageFlowIdFromSomewhere();
        /// MyPageFlow myPageFlow = PageFlowDirectory.Provider.GetPageFlow(myPageFlowId);
        /// </code>
        /// </example>
        /// <param name="instanceId">The instance id of the page flow to retrieve</param>
        /// <returns>The page flow instance corresponding to the instance id</returns>
        /// <remarks>
        /// <para>
        /// If <paramref name="instanceId"/> specifies an instance that is not currently in the store the method throws.
        /// </para>
        /// </remarks>        
        /// <exception cref="PageFlowException">The page flow instance was not found in the store</exception>
		public IPageFlow GetPageFlow(Guid instanceId)
		{
            string pageFlowTypeName = _store.GetInstanceType(instanceId);
            if (pageFlowTypeName == null)
                throw new PageFlowException(String.Format(CultureInfo.CurrentCulture, Resources.InstanceNotFound, instanceId));
            
            return GetPageFlow(Type.GetType(pageFlowTypeName), instanceId);
		}

        private IPageFlow GetPageFlow(Type pageFlowType, Guid instanceId)
        {
            IPageFlow instance = _pageFlowFactory.GetPageFlow(pageFlowType, instanceId);
            RegisterPageFlowEventHandlers(instance);
            return instance;
        }

        private IPageFlow CreatePageFlow(Type pageFlowType)
        {
            IPageFlow instance = _pageFlowFactory.GetPageFlow(pageFlowType);
            _store.Add(instance);
            RegisterPageFlowEventHandlers(instance);
            return instance;
        }

		private void RegisterPageFlowEventHandlers(IPageFlow pageFlow)
		{
			pageFlow.Aborted += new EventHandler<PageFlowAbortedEventArgs>(OnPageFlowAborted);
			pageFlow.Started += new EventHandler<PageFlowEventArgs>(OnPageFlowStarted);
			pageFlow.Suspended += new EventHandler<PageFlowEventArgs>(OnPageFlowSuspended);
			pageFlow.Completed += new EventHandler<PageFlowEventArgs>(OnPageFlowCompleted);
            pageFlow.Resumed += new EventHandler<PageFlowEventArgs>(OnPageFlowResumed);
		}
        
		private void OnPageFlowCompleted(object sender, PageFlowEventArgs e)
		{
			_store.Remove(e.PageFlow);
		}

		private void OnPageFlowSuspended(object sender, PageFlowEventArgs e)
		{
			_store.SetPageFlowNotRunning(e.PageFlow);
		}

        private void OnPageFlowResumed(object sender, PageFlowEventArgs e)
        {
            _store.SetPageFlowRunning(e.PageFlow);
        }

		private void OnPageFlowStarted(object sender, PageFlowEventArgs e)
		{
			_store.SetPageFlowRunning(e.PageFlow);
		}

		private void OnPageFlowAborted(object sender, PageFlowAbortedEventArgs e)
		{
			_store.Remove(e.AbortedPageFlowId);
		}

        /// <summary>
        /// Disposes the underlying <see cref="WorkflowFoundationPageFlowFactory"/>.
        /// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

        /// <summary>
        /// Disposing method as used in the Dispose pattern.
        /// </summary>
        /// <param name="disposing">True if we are called during Dispose. False if we are called from finalizer</param>
        protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				_pageFlowFactory.Dispose();
			}
		}		

        /// <summary>
        /// Process the request url with the page flow engine. 
        /// </summary>
        /// <param name="url">The request url</param>
        /// <returns>The <see cref="ProcessResult" /> holding the redirect action to take.</returns>        
        /// <remarks>
        /// <para>If <paramref name="url"/> belongs to a page flow defined in the catalog, the page flow instance will be resumed if it was suspended.</para>
        /// <para>If <paramref name="url"/> is different from the current page of the running page flow instance, 
        /// the user will be redirected to the current page if the page flow is constrained.</para>
        /// <para>If the user is leaving a page flow, the running instance will be suspended if the <see cref="AbandonBehavior"/> is AllowAndSaveInstance</para>
        /// <para>- or -</para>
        /// <para>Will be aborted if the <see cref="AbandonBehavior"/> is AllowAndDiscardInstance.</para>
        /// <para>- or -</para>
        /// <para>The user will be redirected back to the current page if the <see cref="AbandonBehavior"/> is Prevent.</para>
        /// <para>This method is intended to be called by the <see cref="PageFlowHttpModule"/>.</para>
        /// </remarks>
		public ProcessResult ProcessRequest(string url)
		{
			Guid instanceId = _store.GetLastRunningInstance();  
          
			if (instanceId != Guid.Empty)
			{
                IPageFlow instance = GetPageFlow(instanceId);
                return ProcessWithRunningInstance(instance, url);
			}
			else
			{
				return ProcessWithNoRunningInstance(url);
			}
		}

        /// <summary>
        /// Process the request when there is no instance in the store or the instance is NOT running.
        /// </summary>
        /// <param name="url">The request url.</param>
        /// <returns>The <see cref="ProcessResult" /> holding the redirect action to take.</returns>        
		private ProcessResult ProcessWithNoRunningInstance(string url)
		{
            ProcessResult result;
			IPageFlowDefinition definition = PageFlowDirectory.Catalog.GetByUrl(url);
			if (definition != null)
			{
                IPageFlow instance = GetPageFlow(definition.PageFlowType, false);
                if (instance == null)
                {
                    result = ProcessWithNonExistingInstance(definition);
                }
                else
                {
                    result = ProcessWithExistingInstance(definition, instance);
                }
			}
			else
			{
                result = new ProcessResult(false, string.Empty); ;
			}

			return result;
		}        
        
        /// <summary>
        /// Process the request with a running page flow instance.
        /// </summary>
        /// <param name="instance">The running page flow instance.</param>
        /// <param name="url">The request url.</param>
        /// <returns>The <see cref="ProcessResult" /> holding the redirect action to take.</returns>        
		private ProcessResult ProcessWithRunningInstance(IPageFlow instance, string url)
		{
			ProcessResult result = new ProcessResult(false, string.Empty);

			if (instance.CurrentPage.Url != url)
			{
				if (instance.Definition.ContainsUrl(url))
				{
					Page requestedPage = instance.Definition.GetPageFromUrl(url);
					if (instance.CanRedirectTo(requestedPage.Name))
					{
						instance.SetCurrentPage(requestedPage.Name);
					}
					else
					{
						result = new ProcessResult(true, instance.CurrentPage.Url);
					}
				}
				else
				{
                    switch (instance.Definition.Abandonable)
                    {
                        case AbandonBehavior.AllowAndDiscardInstance :
                            instance.Abort(false);
                            result = ProcessRequest(url);
                            break;
                        case AbandonBehavior.AllowAndSaveInstance :
                            instance.Suspend();
                            result = ProcessRequest(url);
                            break;
                        case AbandonBehavior.Prevent :
                            result = new ProcessResult(true, instance.CurrentPage.Url);
                            break;
                    }                    
				}
			}
			return result;
		}

        /// <summary>
        /// Process the request using the specified instance.
        /// </summary>
        /// <param name="definition">The page flow definition corresponding to the request url.</param>
        /// <param name="instance">The existing page flow instance.</param>
        /// <returns>The <see cref="ProcessResult" /> holding the redirect action to take.</returns>        
        private ProcessResult ProcessWithExistingInstance(IPageFlowDefinition definition, IPageFlow instance)
        {
            ProcessResult result;
            switch (definition.Abandonable)
            {
                case AbandonBehavior.AllowAndSaveInstance:
                    result = ResumeInstance(instance, definition);
                    break;
                default:
                    result = new ProcessResult(true, definition.NotRunningRedirect);
                    break;
            }
            return result;
        }

        private ProcessResult ProcessWithNonExistingInstance(IPageFlowDefinition definition)
        {
            ProcessResult result;
            if (HttpContext.Current != null && HttpContext.Current.Request.HttpMethod.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
            {
                // NOTE: this special case happens when there is no running instance,
                //       there are no suspended instances, and the url belongs to a page flow
                //       but we are on PostBack. In this case we let the request flow, if not we
                //       send the user to NotRunning url.
                result = new ProcessResult(false, string.Empty);
            }
            else
            {
                result = new ProcessResult(true, definition.NotRunningRedirect);
            }
            return result;
        }

        private ProcessResult ResumeInstance(IPageFlow instance, IPageFlowDefinition definition)
        {
            ProcessResult result;
            if (instance.Status == PageFlowStatus.Suspended)
            {
                instance.Resume();
                result = new ProcessResult(false, string.Empty);
            }
            else
            {
                result = new ProcessResult(true, definition.NotRunningRedirect);
            }
            return result;
        }        

	}
}
